﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;

namespace ColorWheelNET.Renderers
{
    internal static class RectangleWheel
    {
        #region Constants

        const int _DISPLAY_BUFFER = 2;
        const double _DEGREES_IN_CIRCLE = 360;

        static double _RADIANS_MULTIPLIER = (Math.PI / ((float)180));
        static double _ANGLE_STEP = 5;
        private static double _DEFAULT_VALUE = 1.0;
        private static double _DEFAULT_SATURATION = 1.0;

        #endregion

        // TODO: 
        // fix 3 bugs
        // Bug#1: When dragging the mouse downward direction (toward Value turns black), until you touch the edge, somehow the Sat/Value Slider jumps up to the top
               //halfHeight needs to be a double since your rectangle can have odd numbered height

        // Bug#2: At RectWheel view, When hit the Okay button, Sat/Value slider color turns into gray scale >.>
            //The selectedPoint was not set yet when the color was assigned in code (rather than with a mouse click)
             //I changed the implementation on SelectedPoint on CWheel.cs to call the CalculateSelectedPoint if its not set

        // Bug#3: At RectWheel, set click to a point, change to SatWheel, then change back to RectWheel, sometimes it changes the pos ... probably math error at calselectpoint .. 
                //You were overcompensating for the halfHeight
                //I changed  rect.Y + (halfHeight - dy);   to    pty = dy;
                //and
                // rect.Y + (halfHeight + dy);   to   rect.Bottom - dy;

            //Rather than think of a seperation at the midpoint where you go up or down from, its easier to think of it this way:
            //For Saturation you start at 0 and INCREASE in Y based on your dy
            //For Value you start at the Bottom of the rec, and DECREASE in Y based on your dy

        /// <summary>
        /// Calculate selected point based on wheel color
        /// </summary>
        /// <param name="wheel"></param>
        internal static void CalculateSelectedPoint(CWheel wheel)
        {
            Rectangle rect = wheel.ClientRectangle;
            double halfHeight = (double)(rect.Height / 2d);
            Color color = wheel.Color;

            // special handling for black and white colors
            // Doing compare per color bit instead of to Color.White because:
            // When use mouse and click to the top left corner, the wheel.Color.Name = "ffffff" ARGB = 255,255,255, but it's not pointed to Color.White
            if (color == Color.White ||
                (color.A == Color.White.A && 
                color.R == Color.White.R && 
                color.G == Color.White.G &&
                color.B == Color.White.B))
            {
                wheel.SelectedPoint = new Point(1, 0);
                return;
            }

            if (color == Color.Black ||
                (color.A == Color.Black.A &&
                color.R == Color.Black.R &&
                color.G == Color.Black.G &&
                color.B == Color.Black.B))
            {
                wheel.SelectedPoint = new Point(1, rect.Bottom);
                return;
            }

            double colorH, colorS, colorV;
            ColorHelper.HSVFromRGB(color, out colorH, out colorS, out colorV);
            float pos = (float)colorH / 360;
            int dx = (int)(pos * rect.Width);
            int ptx = rect.X + dx;
            int dy = 0;
            int pty = (int)(rect.Y + halfHeight); // default pty to half height line

            if (colorS < 1)
            {
                dy = (int)(colorS * halfHeight);
                pty = dy;//;rect.Y + (halfHeight - dy);
            }
            else if (colorV < 1)
            {
                dy = (int)(colorV * halfHeight);
                pty = rect.Bottom - dy;// rect.Y + (halfHeight + dy);
            }

            wheel.SelectedPoint = new Point(ptx, pty);
        }

        /// <summary>
        /// Calculate selector color based on where's it at
        /// Like if it's at dark color area, make selector brighter ...etc...
        /// </summary>
        /// <param name="wheel"></param>
        internal static void CalculateSelectorColor(CWheel wheel)
        {
            Color color = wheel.Color;
            double h, s, v;
            ColorHelper.HSVFromRGB(color, out h, out s, out v);

            if (color == Color.White ||
               (color.A == Color.White.A &&
               color.R == Color.White.R &&
               color.G == Color.White.G &&
               color.B == Color.White.B) || 
               s < 0.5)
            {
                wheel.SelectorColor = Color.DarkSlateGray;
                return;
            }

            if (color == Color.Black ||
                (color.A == Color.Black.A &&
                color.R == Color.Black.R &&
                color.G == Color.Black.G &&
                color.B == Color.Black.B) ||
                v < 0.5)
            {
                wheel.SelectorColor = Color.WhiteSmoke;
                return;
            }

            h = (int)h; // prevent h has a value sometimes like 40.0000000007 which is still > 40 and it cause flickering at the h=40 line

            if (h >= 0 && h <= 40 || (h >= 200 && h <=360))
            {
                wheel.SelectorColor = Color.WhiteSmoke;
            }
            else if (h > 40 && h < 200)
            {
                wheel.SelectorColor = Color.DimGray;
                
                if (v < 0.8)
                {
                    wheel.SelectorColor = Color.WhiteSmoke;
                }
                else if (s < 0.8)
                {
                    wheel.SelectorColor = Color.DimGray;
                }
            }
        }

        internal static void CalculateColorGroup(CWheel wheel, Point pt)
        {
            Rectangle rect = wheel.ClientRectangle;

            if (!rect.Contains(pt)) { return; }

            int ptx = pt.X - rect.X;
            int width = wheel.ClientRectangle.Width;
            float pos = (float)ptx * 1 / width;
            int cindex = (int)(pos * wheel.Colors.Length);
            wheel.ColorGroup = wheel.Colors[cindex];
        }
        
        internal static void CalculateLayout(CWheel wheel)
        {
            List<Color> colors = new List<Color>();
            List<float> poss = new List<float>();

            Rectangle wRec = wheel.WheelRectangle;
            float radius = (float)(wRec.Width / 2);

            Color lastColor = Color.White;
            float lastPos = 0.0f;

            for (double angle = 0; angle < _DEGREES_IN_CIRCLE; angle += _ANGLE_STEP)
            {
                double radians = angle * _RADIANS_MULTIPLIER;

                try
                {
                    lastColor = ColorHelper.ColorFromHSV(angle, _DEFAULT_SATURATION, _DEFAULT_VALUE);
                    lastPos = (float)(radians / (Math.PI * 2));
                }
                finally
                {
                    colors.Add(lastColor);
                    poss.Add(lastPos);
                }
            }

            //TEMP: for positions, last pos must be 1.0
            poss.RemoveAt(poss.Count - 1);
            poss.Add(1.0f);

            wheel.Colors = colors.ToArray();
            wheel.Positions = poss.ToArray();


            if (wheel.SelectedPoint.IsEmpty && wheel.OnCalculateSelectedPoint != null)
            {
                wheel.OnCalculateSelectedPoint(wheel);
            }

            if (wheel.SelectorColor.IsEmpty && wheel.OnCalculateSelectorColor != null)
            {
                wheel.OnCalculateSelectorColor(wheel);
            }
        }
        
        internal static void DrawWheel(CWheel wheel, Graphics graphics)
        {
            if (wheel.Colors == null) { return; }

            Rectangle rect = wheel.ClientRectangle;

            using (LinearGradientBrush b = new LinearGradientBrush(rect, Color.Transparent, Color.Transparent, 0, false))
            {
                ColorBlend cb = new ColorBlend(wheel.Colors.Length);
                cb.Colors = wheel.Colors;
                cb.Positions = wheel.Positions;
                b.InterpolationColors = cb;
                graphics.FillRectangle(b, rect);

            }

            using (LinearGradientBrush b = new LinearGradientBrush(rect, Color.Transparent, Color.Transparent, 90, false))
            {
                ColorBlend cb = new ColorBlend();
                cb.Colors = new Color[] {Color.White, Color.Transparent, Color.Black};
                cb.Positions = new float[] { 0.0f, 0.5f, 1.0f };
                b.InterpolationColors = cb;
                graphics.FillRectangle(b, rect);
            }
        }

        /// <summary>
        /// Temporary draw a solid circle when select
        /// Can't use SaturationWheel.ColorHitTest because it limits to circle area selection, not rectangle
        /// </summary>
        /// <param name="wheel"></param>
        /// <param name="pt"></param>
        internal static void ColorHitTest(CWheel wheel, Point pt)
        {
            Rectangle rect = wheel.ClientRectangle;

            if (!rect.Contains(pt)) { return; }

            double halfHeight = (double)(rect.Height / 2d);

            int ptx = pt.X - rect.X;
            int width = wheel.ClientRectangle.Width;
            float pos = (float)ptx / width;
            int cindex = (int)(pos * wheel.Colors.Length);
            Color group = wheel.Colors[cindex];

            double colorH, colorS, colorV;
            ColorHelper.HSVFromRGB(group, out colorH, out colorS, out colorV);
            int pty = pt.Y - rect.Y;

            if (pty < halfHeight)
            {
                double dy = halfHeight - pty;
                colorS = 1 - (double)dy / halfHeight;
            }

            else if (pty > halfHeight)
            {
                double dy = pty - halfHeight;
                colorV = 1 - (double)dy / halfHeight;
            }

            Color color = ColorHelper.ColorFromHSV(colorH, colorS, colorV);
            wheel.SetSelectedColor(color, pt, group);
        }
    }
}
